##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary

  # Exploit mixins should be called first
  include Msf::Exploit::Remote::DCERPC

  include Msf::Auxiliary::Report

  # Scanner mixin should be near last
  include Msf::Auxiliary::Scanner

  def initialize
    super(
      'Name'        => 'Hidden DCERPC Service Discovery',
      'Description' => %q{
        This module will query the endpoint mapper and make a list
      of all ncacn_tcp RPC services. It will then connect to each of
      these services and use the management API to list all other
      RPC services accessible on this port. Any RPC service found attached
      to a TCP port, but not listed in the endpoint mapper, will be displayed
      and analyzed to see whether anonymous access is permitted.
      },
      'Author'      => 'hdm',
      'License'     => MSF_LICENSE
    )

    deregister_options('RHOST', 'RPORT')
  end

  # Obtain information about a single host
  def run_host(ip)
    begin

      epm = dcerpc_endpoint_list()
      if(not epm)
        print_status("Could not contact the endpoint mapper on #{ip}")
        return
      end

      eports = {}

      epm.each do |ep|
        next if !(ep[:port] and ep[:prot] and ep[:prot] == "tcp")
        eports[ep[:port]] ||= {}
        eports[ep[:port]][ep[:uuid]+'_'+ep[:vers]] = true
      end

      eports.each_pair do |eport, servs|

        rport = eport
        print_status("Looking for services on #{ip}:#{rport}...")

        ids = dcerpc_mgmt_inq_if_ids(rport)
        return if not ids

        ids.each do |id|
          if (not servs.has_key?(id[0]+'_'+id[1]))
            print_status("\tHIDDEN: UUID #{id[0]} v#{id[1]}")

            conn = nil
            bind = nil
            call = nil
            data = nil
            error = nil
            begin
              connect(true, { 'RPORT' => eport })
              conn = true

              handle = dcerpc_handle(id[0], id[1], 'ncacn_ip_tcp', [eport])
              dcerpc_bind(handle)
              bind = true

              res = dcerpc.call(0, NDR.long(0) * 128)
              call = true

              if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
                data = dcerpc.last_response.stub_data
              end

            rescue ::Interrupt
              raise $!
            rescue ::Exception => e
              error = e.to_s
            end

            if (error and error =~ /DCERPC FAULT/ and error !~ /nca_s_fault_access_denied/)
              call = true
            end

            status = "\t\t"
            status << "CONN " if conn
            status << "BIND " if bind
            status << "CALL " if call
            status << "DATA=#{data.unpack("H*")[0]} " if data
            status << "ERROR=#{error} " if error

            print_status(status)
            print_status("")

            ## Add Report
            report_note(
              :host   => ip,
              :proto  => 'tcp',
              :port   => datastore['RPORT'],
              :type   => "DCERPC HIDDEN: UUID #{id[0]} v#{id[1]}",
              :data   => status
            )

          end
        end
      end

    rescue ::Interrupt
      raise $!
    rescue ::Exception => e
      print_status("Error: #{e}")
    end
  end


end
